package com.sen.gulimall.seckill.utils;

import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.util.Random;

/**
 * @author Kisen
 * @email liuqs@jaid.cn
 * @date 2022/8/28 17:25
 * @detail 分布式ID生成器（雪花算法）
 */
public class SnowflakeIdGenerator {


    /**
     * 时间戳标识所占二进制位数
     */
    private static final int TIME_STAMP_BIT_LEN = 41;
    /**
     * 机房标识所占二进制位数
     */
    private static final int SERVER_ROOM_BIT_LEN = 5;
    /**
     * 服务器标识所占二进制位数
     */
    private static final int SERVER_BIT_LEN = 5;
    /**
     * 每毫秒中序列所占二进制位数
     */
    private static final int SEQ_BIT_LEN = 12;

    /**
     * 时间戳标识向左移动的位数（这里的1标识最高位）
     */
    private static final int TIME_STAMP_LEFT_BIT_LEN = 64 - 1 - TIME_STAMP_BIT_LEN;
    /**
     * 机房标识左移位数
     */
    private static final int SERVER_ROOM_LEFT_BIT_LEN = TIME_STAMP_LEFT_BIT_LEN - SERVER_ROOM_BIT_LEN;
    /**
     * 服务器标识左移位数
     */
    private static final int SERVER_LEFT_BIT_LEN = SERVER_ROOM_LEFT_BIT_LEN - SERVER_BIT_LEN;

    /**
     * 开始时间戳，此处为 2022年8月28日
     */
    private static final long START_TIME_STAMP = 1661616000000L;
    /**
     * 上次生成ID的时间戳
     */
    private static long LAST_TIME_STAMP = -1L;
    /**
     * 上一次毫秒内存序列值
     */
    private static long LAST_SEQ = 0L;

    /**
     * 获取机房标识（可以手动定义0-31之间的数）
     */
    private static final long SERVER_ROOM_ID = getServerRoomId();
    /**
     * 获取服务器标识（可以手动定义0-31之间的数）
     */
    private static final long SERVER_ID = getServerId();

    /**
     * 机房标识最大值 +1
     */
    private static final int SERVER_ROOM_MAX_NUM_1 = ~(-1 << SERVER_ROOM_BIT_LEN) + 1;
    /**
     * 服务器标识最大值 +1
     */
    private static final int SERVER_MAX_NUM_1 = ~(-1 << SERVER_BIT_LEN) + 1;
    /**
     * 毫秒内存列的最大值
     */
    private static final long SEQ_MAX_NUM = ~(-1 << SEQ_BIT_LEN);

    /**
     * 对服务器地址的哈希码取余作为服务器标识
     *
     * @return 服务器标识
     */
    private static int getServerId() {
        try {
            String hostAddress = Inet4Address.getLocalHost().getHostAddress();
            return (hostAddress.hashCode() & Integer.MAX_VALUE) % SERVER_MAX_NUM_1;
        } catch (UnknownHostException e) {
            return new Random().nextInt(SERVER_MAX_NUM_1);
        }
    }

    /**
     * 对服务器名称的哈希码取余作为机房标识
     *
     * @return 机房标识
     */
    private static int getServerRoomId() {
        try {
            String hostName = Inet4Address.getLocalHost().getHostName();
            return (hostName.hashCode() & Integer.MAX_VALUE) % SERVER_ROOM_MAX_NUM_1;
        } catch (UnknownHostException e) {
            return new Random().nextInt(SERVER_ROOM_MAX_NUM_1);
        }
    }

    /**
     * 一直循环直到获取到下毫秒的时间戳
     *
     * @param lastMillis
     * @return 下一毫秒的时间戳
     */
    private static long nextMillis(long lastMillis) {
        long now = System.currentTimeMillis();
        while (now <= lastMillis) {
            now = System.currentTimeMillis();
        }
        return now;
    }

    /**
     * 生成唯一ID
     * 须加锁避免并发问题
     *
     * @return 返回唯一ID
     */
    public synchronized static long generateUniqueId() {
        long currentTimeStamp = System.currentTimeMillis();
        // 如果当前时间小于上一次ID生成的时间戳，说明系统时间回退过，此时应抛出异常
        if (currentTimeStamp < LAST_TIME_STAMP) {
            throw new RuntimeException(String.format("系统时间错误！ %d 毫秒内拒绝生成雪花ID", START_TIME_STAMP));
        }
        if (currentTimeStamp == LAST_TIME_STAMP) {
            LAST_SEQ = (LAST_SEQ + 1) & SEQ_MAX_NUM;
            if (LAST_SEQ == 0) {
                currentTimeStamp = nextMillis(LAST_TIME_STAMP);
            }
        } else {
            LAST_SEQ = 0;
        }
        // 上次生成ID的时间戳
        LAST_TIME_STAMP = currentTimeStamp;
        return ((currentTimeStamp - START_TIME_STAMP) << TIME_STAMP_LEFT_BIT_LEN | (SERVER_ROOM_ID << SERVER_ROOM_LEFT_BIT_LEN) | (SERVER_ID << SERVER_LEFT_BIT_LEN) | LAST_SEQ);
    }

    /**
     * 主函数测试
     *
     * @param args
     */
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        int num = 100;
        for (int i = 0; i < num; i++) {
            System.out.println(generateUniqueId());
        }
        long end = System.currentTimeMillis();

        System.out.println("共生成 " + num + " 个ID，用时 " + (end - start) + " 毫秒");
    }
}
